﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Common;
using System.Data;
using BPMS.Utility.Converter;
using BPMS.Utility.Logging;
using BPMS.Utility.Reflections;

namespace BPMS.Core.DataAccess
{
    public class DbHelper : IDisposable
    {
        private bool isDisposed = false;
        private readonly int timeout;
        private readonly string connectionString;
        private readonly DbProviderFactory ProviderFactory;
        private readonly DbConnectionProvider ConnectionProvider;
        private readonly DbTransactionProvider TransactionProvider;
        private readonly DbParameterProvider ParameterProvider;

        public bool UseTransaction { get; private set; }

        public DbHelper()
            : this(ConnectionInfo.DefaultConn)
        {

        }

        public DbHelper(ConnectionInfo connectionInfo)
        {
            this.timeout = int.MinValue;
            this.ProviderFactory = DbProviderFactories.GetFactory(connectionInfo.DbProviderName);
            this.connectionString = connectionInfo.ConnectionString;
            ConnectionProvider = new DbConnectionProvider(ProviderFactory, connectionString);
            TransactionProvider = new DbTransactionProvider();
            ParameterProvider = new DbParameterProvider(ProviderFactory);
        }

        public DbHelper(ConnectionInfo connectionInfo, int timeout)
            : this(connectionInfo)
        {
            this.timeout = timeout;
        }

        public T CreateCommand<T>(Func<DbCommand, T> executeHandler, string commandText, IEnumerable<DbParameterWrapper> parameters = null, CommandType commandType = CommandType.Text)
        {
            try
            {
                WriteDbExcuteLog(commandText, parameters);
                using (DbCommand command = ProviderFactory.CreateCommand())
                {
                    command.CommandText = commandText;
                    command.CommandType = commandType;
                    command.Connection = ConnectionProvider.OpenConnection();

                    if (UseTransaction)
                    {
                        if (TransactionProvider.Transaction == null)
                        {
                            command.Transaction = TransactionProvider.CreateTransaction(command.Connection);
                        }
                    }

                    if (timeout != int.MinValue)
                    {
                        command.CommandTimeout = timeout;
                    }

                    if (null != parameters && parameters.Count() >= 1)
                    {
                        command.Parameters.AddRange(ParameterProvider.GetParameterCollection(parameters));
                    }

                    return executeHandler(command);
                }
            }
            catch (Exception ex)
            {
                RollbackTransaction();
                LogHelper.DbExcuteError(commandText, ex);
            }
            finally
            {
                if (!UseTransaction)
                {
                    ConnectionProvider.CloseConnection();
                }
            }
            return default(T);
        }

        public List<T> ExecuteReader<T>(string commandText, IEnumerable<DbParameterWrapper> parameters = null, CommandType commandType = CommandType.Text)
        {

            return CreateCommand((command) =>
            {
                using (DbDataReader dataReader = command.ExecuteReader())
                {
                    List<T> list = new List<T>();
                    while (dataReader.Read())
                    {
                        list.Add((T)ReflectionUtility.CreateInstance<T>(dataReader));
                    }
                    return list;
                }
            }, commandText, parameters, commandType);
        }

        public T ExecuteReaderSingle<T>(string commandText, IEnumerable<DbParameterWrapper> parameters = null, CommandType commandType = CommandType.Text)
        {
            return CreateCommand((command) =>
            {
                using (DbDataReader dataReader = command.ExecuteReader())
                {
                    return (T)ReflectionUtility.CreateInstance<T>(dataReader);
                }
            }, commandText, parameters, commandType);
        }

        public int ExecuteNonQuery(string commandText, IEnumerable<DbParameterWrapper> parameters = null, CommandType commandType = CommandType.Text)
        {

            return CreateCommand((command) =>
            {
                return command.ExecuteNonQuery();
            }, commandText, parameters, commandType);
        }

        public object ExecuteScalar(string commandText, IEnumerable<DbParameterWrapper> parameters = null, CommandType commandType = CommandType.Text)
        {

            return CreateCommand<object>((command) =>
            {
                return command.ExecuteScalar();
            }, commandText, parameters, commandType);
        }

        public T ExecuteScalar<T>(string commandText, IEnumerable<DbParameterWrapper> parameters = null, CommandType commandType = CommandType.Text)
        {

            return TypeParser.ToObject<T>(ExecuteScalar(commandText, parameters, commandType));
        }

        public object ExecuteIdentity(string commandText, IEnumerable<DbParameterWrapper> parameters = null, CommandType commandType = CommandType.Text)
        {
            commandText = commandText + Environment.NewLine + "select SCOPE_IDENTITY()";
            return ExecuteScalar(commandText, parameters, commandType);
        }

        public DataTable ExecuteDataTable(string commandText, IEnumerable<DbParameterWrapper> parameters = null, CommandType commandType = CommandType.Text)
        {
            return CreateCommand<DataTable>((command) =>
            {
                DataTable data = new DataTable();
                DbDataAdapter dataAdapter = ProviderFactory.CreateDataAdapter();
                dataAdapter.SelectCommand = command;
                dataAdapter.Fill(data);
                return data;
            }, commandText, parameters, commandType);
        }

        public DataSet ExecuteDataSet(string commandText, IEnumerable<DbParameterWrapper> parameters = null, CommandType commandType = CommandType.Text)
        {
            return CreateCommand<DataSet>((command) =>
            {
                DataSet data = new DataSet();
                DbDataAdapter dataAdapter = ProviderFactory.CreateDataAdapter();
                dataAdapter.SelectCommand = command;
                dataAdapter.Fill(data);
                return data;
            }, commandText, parameters, commandType);
        }

        public void BeginTransaction()
        {
            this.UseTransaction = true;
        }

        public void BeginTransaction(IsolationLevel isolationLevel)
        {
            this.BeginTransaction();
            TransactionProvider.IsolationLevel = isolationLevel;
        }

        public void RollbackTransaction()
        {
            this.UseTransaction = false;
            TransactionProvider.RollbackTransaction();
        }

        public void CommitTransaction()
        {
            this.UseTransaction = false;
            TransactionProvider.CommitTransaction();
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        private void Dispose(bool isDisposing)
        {
            if (isDisposing)
            {
                if (!isDisposed)
                {
                    TransactionProvider.Dispose();
                    ConnectionProvider.Dispose();
                    isDisposed = true;
                }
            }
        }

        private void WriteDbExcuteLog(string commandText, IEnumerable<DbParameterWrapper> parameters)
        {
            string strParam = string.Empty;
            if (null != parameters)
            {
                strParam = string.Join(",", parameters.Select(item => item.ParameterName + "=" + item.ParameterValue));
            }
            LogHelper.DbExcuteRecord(commandText, strParam);
        }
    }
}
